<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>tiptap Mention 提及节点 - itxst.com</title>
    <meta name="keywords" content="tiptap Mention 提及节点" />
    <meta name="description" content="tiptap Mention 提及节点可以实现像聊天软件的@某人一样的功能，你可以通过前端实现数据筛选也可以调用后端api返回需要的数据。" />
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
    <meta http-equiv="Content-Language" content="zh-CN">
    <link rel="shortcut icon" href="/img/favicon.ico" type="image/x-icon" />
    <link href="/css/zt.css?ver=1.6.6" rel="stylesheet" type="text/css" media="all" />
    <script type="text/javascript" language="javascript" src="/js/jquery.js?ver=1.6.6"></script>
    <script type="text/javascript" src="/js/highlight/highlight.min.js?ver=1.6.6"></script>
    <link rel="stylesheet" type="text/css" href="/js/highlight/styles/vs2015x.css?ver=1.6.6">
    <script type="text/javascript" language="javascript" src="/js/comm.js?ver=1.6.6"></script>
    <script type="text/javascript" language="javascript" src="/js/zt.js?ver=1.6.6"></script>
    <script type="text/javascript" charset="utf-8" src="/js/layer/layer.js?ver=1.6.6"></script>
    <link rel="alternate" media="only screen and(max-width: 640px)" href="http://m.itxst.com/tiptap/nnzya3qj.html" >
    <!--[if lte IE 9]>
    <script src="/js/respond/respond.js?ver=1.6.6"></script>
    <![endif]-->
</head>
<body>
    <div class="body"  >
        <div class="header">
            <div class="naver">
                <div class="logo">
                    <a href="/"><img src="/img/logov31.png" /></a>
                </div>
                <div class="naver-left">
                 <a href="/tiptap/tutorial.html">tiptap 中文文档</a>
                </div>
                <div class="naver-search">
                    <div class="naver-search-txt">
                        <input type="text" id="keyword" placeholder="Please enter the keyword" data-topic="tiptap">
                    </div>
                    <div class="naver-search-btn"><a href="javascript:void(0)" onclick="Search()">Search</a></div>
                </div>
                 <div class="naver-right"> 
                        <a href="/ag-grid/tutorial.html">AG-GRID</a> 
                        <a href="/tiptap/tutorial.html">Tiptap</a> 
                        <a href="/css-flex/tutorial.html">CSS-FLEX</a> 
                        <a href="/Bootstrap-Table/QuickStart.html">Bootstrap-Table</a> 
                </div>
            </div>
            <div class="path">
                
            </div>
        </div>
        <div class="clearfix"></div>
        <div class="pagew">
        <div class="page" >
            <div class="page-body">
                <div class="content-detail">
                    <h1>tiptap Mention 提及节点</h1>
               
               <p class="p">tiptap <b>Mention</b> 提及节点可以实现像聊天软件的<b>@</b>某人一样的功能，你可以通过<b>前端数据筛选</b>也可以调用<b>后端api</b>返回需要的数据，它依赖 tippy.js。</p><h2>Install 安装</h2><pre class="brush:js;toolbar:false"><code class="language-javascript">npm install @tiptap/extension-mention</code></pre><h2>依赖库</h2><p class="pe">为了准确的弹框，我们在示例中使用了<b>tippy.js</b>，当然你可以使用你自己的库。</p><pre class="brush:js;toolbar:false"><code class="language-javascript">npm install tippy.js</code></pre><p class="pe">候选词功能需要手动安装<b>@tiptap/suggestion</b>扩展</p><pre class="brush:js;toolbar:false"><code class="language-javascript">npm install @tiptap/suggestion</code></pre><h2>Settings 配置</h2><p class="pe"><b>renderLabel</b> 定义如何呈现提及标签。</p><pre class="brush:js;toolbar:false"><code class="language-javascript">Mention.configure({
  renderLabel({ options, node }) {
    return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
  }
})</code></pre><p class="pe"><b>suggestion</b>&nbsp;候选词相关函数库，具体用法请查看例子，或查看对应文档。</p><pre class="brush:js;toolbar:false"><code class="language-javascript">Mention.configure({
  suggestion: {
    // …
  },
})</code></pre><p class="pe"><b>HTMLAttributes</b> 自定义标签对应的HTML属性。</p><pre class="brush:js;toolbar:false"><code class="language-javascript">Mention.configure({
  HTMLAttributes: {
    class: 'custom-class',
  },
})</code></pre><h2>源代码</h2><p><a href="https://github.com/ueberdosis/tiptap/blob/main/packages/extension-mention/" target="_blank" textvalue="mention 源代码">mention 源代码</a></p><h2>在线例子</h2><p><a href="https://debug.itxst.com/static/tiptapdemo/index.html#mention" target="_blank" textvalue="vue 在线例子">vue 在线例子</a></p><h2>Image 例子</h2><div class="mcode"><ul class=" list-paddingleft-2"><li><p class="mcode-curr" style="padding:0px 6px">Index.vue</p></li><li><p style="padding:0px 6px">MentionList.vue</p></li><li><p style="padding:0px 6px">suggestion.js</p></li><li><p style="padding:0px 6px">index.jsx</p></li><li><p style="padding:0px 6px">MentionList.jsx</p></li><li><p style="padding:0px 6px">styles.scss</p></li><li><p style="padding:0px 6px">suggestion.js</p></li></ul><pre class="brush:html;toolbar:false;"><code class="language-html">&lt;!-- vue例子 --&gt;
&lt;template&gt;
  &lt;div v-if="editor"&gt;
    &lt;editor-content :editor="editor" /&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
import Mention from '@tiptap/extension-mention'
import StarterKit from '@tiptap/starter-kit'
import { Editor, EditorContent } from '@tiptap/vue-3'
import suggestion from './suggestion'
export default {
  components: {
    EditorContent,
  },
  data() {
    return {
      editor: null,
    }
  },
  mounted() {
    this.editor = new Editor({
      extensions: [
        StarterKit,
        Mention.configure({
          HTMLAttributes: {
            class: 'mention',
          },
          suggestion,
        }),
      ],
      content: `
        &lt;p&gt;Hi everyone! Don’t forget the daily stand up at 8 AM.&lt;/p&gt;
        &lt;p&gt;&lt;span data-type="mention" data-id="Jennifer Grey"&gt;&lt;/span&gt; Would you mind to share what you’ve been working on lately? We fear not much happened since Dirty Dancing.
        &lt;p&gt;&lt;span data-type="mention" data-id="Winona Ryder"&gt;&lt;/span&gt; &lt;span data-type="mention" data-id="Axl Rose"&gt;&lt;/span&gt; Let’s go through your most important points quickly.&lt;/p&gt;
        &lt;p&gt;I have a meeting with &lt;span data-type="mention" data-id="Christina Applegate"&gt;&lt;/span&gt; and don’t want to come late.&lt;/p&gt;
        &lt;p&gt;– Thanks, your big boss&lt;/p&gt;
      `,
    })
  },
  beforeUnmount() {
    this.editor.destroy()
  },
}
&lt;/script&gt;
&lt;style&gt;
.ProseMirror {
  &gt; * + * {
    margin-top: 0.75em;
  }
}
.mention {
  border: 1px solid #000;
  border-radius: 0.4rem;
  padding: 0.1rem 0.3rem;
  box-decoration-break: clone;
}
&lt;/style&gt;</code></pre><pre class="brush:html;toolbar:false;"><code class="language-html">&lt;!-- vue例子 --&gt;
&lt;template&gt;
  &lt;div&gt;
    &lt;template v-if="items.length"&gt;
      &lt;button
        :class="{ 'is-selected': index === selectedIndex }"
        v-for="(item, index) in items"
        :key="index"
        @click="selectItem(index)"
      &gt;
        {{ item }}
      &lt;/button&gt;
    &lt;/template&gt;
    &lt;div v-else&gt;
      No result
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  props: {
    items: {
      type: Array,
      required: true,
    },
    command: {
      type: Function,
      required: true,
    },
  },
  data() {
    return {
      selectedIndex: 0,
    }
  },
  watch: {
    items() {
      this.selectedIndex = 0
    },
  },
  methods: {
    onKeyDown({ event }) {
      if (event.key === 'ArrowUp') {
        this.upHandler()
        return true
      }
      if (event.key === 'ArrowDown') {
        this.downHandler()
        return true
      }
      if (event.key === 'Enter') {
        this.enterHandler()
        return true
      }
      return false
    },
    upHandler() {
      this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length
    },
    downHandler() {
      this.selectedIndex = (this.selectedIndex + 1) % this.items.length
    },
    enterHandler() {
      this.selectItem(this.selectedIndex)
    },
    selectItem(index) {
      const item = this.items[index]
      if (item) {
        this.command({ id: item })
      }
    },
  },
}
&lt;/script&gt;
&lt;style&gt;
.items {
  padding: 0.2rem;
  position: relative;
  border-radius: 0.5rem;
  background: #FFF;
  color: rgba(0, 0, 0, 0.8);
  overflow: hidden;
  font-size: 0.9rem;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.05),
    0px 10px 20px rgba(0, 0, 0, 0.1),
  ;
}
.item {
  display: block;
  margin: 0;
  width: 100%;
  text-align: left;
  background: transparent;
  border-radius: 0.4rem;
  border: 1px solid transparent;
  padding: 0.2rem 0.4rem;
  &amp;.is-selected {
    border-color: #000;
  }
}
&lt;/style&gt;</code></pre><pre class="brush:js;toolbar:false;"><code class="language-javascript">// vue例子
import { VueRenderer } from '@tiptap/vue-3'
import tippy from 'tippy.js'
import MentionList from './MentionList.vue'

export default {
  items: ({ query }) =&gt; {
    return [
      'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet',
    ].filter(item =&gt; item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5)
  },
  render: () =&gt; {
    let component
    let popup
    return {
      onStart: props =&gt; {
        component = new VueRenderer(MentionList, {
          // using vue 2:
          // parent: this,
          // propsData: props,
          // using vue 3:
          props,
          editor: props.editor,
        })
        if (!props.clientRect) {
          return
        }
        popup = tippy('body', {
          getReferenceClientRect: props.clientRect,
          appendTo: () =&gt; document.body,
          content: component.element,
          showOnCreate: true,
          interactive: true,
          trigger: 'manual',
          placement: 'bottom-start',
        })
      },
      onUpdate(props) {
        component.updateProps(props)
        if (!props.clientRect) {
          return
        }
        popup[0].setProps({
          getReferenceClientRect: props.clientRect,
        })
      },
      onKeyDown(props) {
        if (props.event.key === 'Escape') {
          popup[0].hide()
          return true
        }
        return component.ref?.onKeyDown(props)
      },
      onExit() {
        popup[0].destroy()
        component.destroy()
      },
    }
  },
}</code></pre><pre class="brush:js;toolbar:false;"><code class="language-javascript">//React例子
import './styles.scss'
import Mention from '@tiptap/extension-mention'
import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import React from 'react'
import suggestion from './suggestion'
export default () =&gt; {
  const editor = useEditor({
    extensions: [
      StarterKit,
      Mention.configure({
        HTMLAttributes: {
          class: 'mention',
        },
        suggestion,
      }),
    ],
    content: `
        &lt;p&gt;Hi everyone! Don’t forget the daily stand up at 8 AM.&lt;/p&gt;
        &lt;p&gt;&lt;span data-type="mention" data-id="Jennifer Grey"&gt;&lt;/span&gt; Would you mind to share what you’ve been working on lately? We fear not much happened since Dirty Dancing.
        &lt;p&gt;&lt;span data-type="mention" data-id="Winona Ryder"&gt;&lt;/span&gt; &lt;span data-type="mention" data-id="Axl Rose"&gt;&lt;/span&gt; Let’s go through your most important points quickly.&lt;/p&gt;
        &lt;p&gt;I have a meeting with &lt;span data-type="mention" data-id="Christina Applegate"&gt;&lt;/span&gt; and don’t want to come late.&lt;/p&gt;
        &lt;p&gt;– Thanks, your big boss&lt;/p&gt;
      `,
  })
  if (!editor) {
    return null
  }
  return &lt;EditorContent editor={editor} /&gt;
}</code></pre><pre class="brush:js;toolbar:false;"><code class="language-javascript">//React例子
import './style.scss'
import React, {
  forwardRef, useEffect, useImperativeHandle,
  useState,
} from 'react'
export default forwardRef((props, ref) =&gt; {
  const [selectedIndex, setSelectedIndex] = useState(0)
  const selectItem = index =&gt; {
    const item = props.items[index]
    if (item) {
      props.command({ id: item })
    }
  }
  const upHandler = () =&gt; {
    setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length)
  }
  const downHandler = () =&gt; {
    setSelectedIndex((selectedIndex + 1) % props.items.length)
  }
  const enterHandler = () =&gt; {
    selectItem(selectedIndex)
  }
  useEffect(() =&gt; setSelectedIndex(0), [props.items])
  useImperativeHandle(ref, () =&gt; ({
    onKeyDown: ({ event }) =&gt; {
      if (event.key === 'ArrowUp') {
        upHandler()
        return true
      }
      if (event.key === 'ArrowDown') {
        downHandler()
        return true
      }
      if (event.key === 'Enter') {
        enterHandler()
        return true
      }
      return false
    },
  }))

  return (
    &lt;div className="items"&gt;
      {props.items.length
        ? props.items.map((item, index) =&gt; (
          &lt;button
            className={`item ${index === selectedIndex ? 'is-selected' : ''}`}
            key={index}
            onClick={() =&gt; selectItem(index)}
          &gt;
            {item}
          &lt;/button&gt;
        ))
        : &lt;div className="item"&gt;No result&lt;/div&gt;
      }
    &lt;/div&gt;
  )
})</code></pre><pre class="brush:css;toolbar:false;"><code class="language-css">//React例子
.items {
  background: #fff;
  border-radius: 0.5rem;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1);
  color: rgba(0, 0, 0, 0.8);
  font-size: 0.9rem;
  overflow: hidden;
  padding: 0.2rem;
  position: relative;
}

.item {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 0.4rem;
  display: block;
  margin: 0;
  padding: 0.2rem 0.4rem;
  text-align: left;
  width: 100%;

  &amp;.is-selected {
    border-color: #000;
  }
}
.ProseMirror {
  &gt; * + * {
    margin-top: 0.75em;
  }
}
.mention {
  border: 1px solid #000;
  border-radius: 0.4rem;
  box-decoration-break: clone;
  padding: 0.1rem 0.3rem;
}</code></pre><pre class="brush:js;toolbar:false;"><code class="language-javascript">//React例子
import { ReactRenderer } from '@tiptap/react'
import tippy from 'tippy.js'
import MentionList from './MentionList.jsx'

export default {
  items: ({ query }) =&gt; {
    return [
      'Lea Thompson',
      'Cyndi Lauper',
      'Tom Cruise',
      'Madonna',
      'Jerry Hall',
      'Joan Collins',
      'Winona Ryder',
      'Christina Applegate',
      'Alyssa Milano',
      'Molly Ringwald',
      'Ally Sheedy',
      'Debbie Harry',
      'Olivia Newton-John',
      'Elton John',
      'Michael J. Fox',
      'Axl Rose',
      'Emilio Estevez',
      'Ralph Macchio',
      'Rob Lowe',
      'Jennifer Grey',
      'Mickey Rourke',
      'John Cusack',
      'Matthew Broderick',
      'Justine Bateman',
      'Lisa Bonet',
    ]
      .filter(item =&gt; item.toLowerCase().startsWith(query.toLowerCase()))
      .slice(0, 5)
  },

  render: () =&gt; {
    let component
    let popup

    return {
      onStart: props =&gt; {
        component = new ReactRenderer(MentionList, {
          props,
          editor: props.editor,
        })

        if (!props.clientRect) {
          return
        }

        popup = tippy('body', {
          getReferenceClientRect: props.clientRect,
          appendTo: () =&gt; document.body,
          content: component.element,
          showOnCreate: true,
          interactive: true,
          trigger: 'manual',
          placement: 'bottom-start',
        })
      },

      onUpdate(props) {
        component.updateProps(props)

        if (!props.clientRect) {
          return
        }

        popup[0].setProps({
          getReferenceClientRect: props.clientRect,
        })
      },

      onKeyDown(props) {
        if (props.event.key === 'Escape') {
          popup[0].hide()
          return true
        }
        return component.ref?.onKeyDown(props)
      },
      onExit() {
        popup[0].destroy()
        component.destroy()
      },
    }
  },
}</code></pre></div>
 
               <h2>
    下载教程 Demo
</h2>
<p class="pe">
   本教程<b>Demo</b>的源码点击这里下载，<a href="https://img1.itxst.com/8/c/8c32fcdc12cb8847c62e405be8a73835.zip" target="_blank" textvalue="Tiptap Demo">Tiptap Demo</a>，下载完成后运行<b>npm i</b>初始化依赖包。
</p>
                </div>
                <div class="content-r">   <a href="/tiptap/tutorial.html" target="_self">
                     <img src="https://img1.itxst.com/0/9/09355b3592a0c16799b504a9f2a70303.png" alt="tiptap 中文文档" />
                    </a>                                                <a href="//www.itxst.com/vue-draggable/tutorial.html" target="_blank">
                                <img src="//img1.itxst.com/b/f/bf9af8368dfac84cd703ef54e1e89d14.png" alt="vue.draggable" />
                            </a>
 
                </div>
            </div>
            <div class="home-menu scroll">
                <div class="tree">
      <a data-level="1" class="leve-1   doc-1   " href="/tiptap/tutorial.html" title="tiptap 中文文档">快速入门</a>
      <a data-level="1" class="leve-1    arrow-1" href="/tiptap/configuration.html" title="Guide 向导">Guide 向导</a>
      <a data-level="1" class="leve-1    arrow-2" href="/tiptap/api.html" title="tiptap api 列表">API 列表</a>
      <a data-level="2" class="leve-2   doc-1   " href="/tiptap/methods.html" title="tiptap editor 方法">tiptap 方法</a>
      <a data-level="2" class="leve-2   doc-1   " href="/tiptap/attrs.html" title="tiptap 属性">tiptap 属性</a>
      <a data-level="2" class="leve-2   doc-1   " href="/tiptap/settings.html" title="titap 配置">titap 配置</a>
      <a data-level="2" class="leve-2    arrow-1" href="/tiptap/uaqmeemr.html" title="tiptap commands 命令">commands 命令</a>
      <a data-level="2" class="leve-2    arrow-2" href="/tiptap/363qzvqv.html" title="nodes 节点">nodes 节点</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/blockquote.html" title="tiptap Blockquote 引用扩展">Blockquote 引用扩展</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/avmrbq6f.html" title="tiptap BulletList 无序序列扩展">BulletList 无序序列扩展</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/mrmuzbin.html" title="tiptap CodeBlock 代码块扩展">CodeBlock 代码块扩展</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/ibyqqqzj.html" title="tiptap CodeBlockLowlight 代码高亮">CodeBlockLowlight 代码高亮</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/effvybzr.html" title="tiptap Document 节点">Document 节点</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/qnv3uzam.html" title="tiptap HardBreak 换行节点">HardBreak 换行节点</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/yvymznqu.html" title="tiptap Heading 标题节点">Heading 标题节点</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/eqbzrb7r.html" title="tiptap HorizontalRule 横线节点">HorizontalRule 横线节点</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/z77rauyz.html" title="tiptap Image 图片扩展">Image 图片扩展</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/bqjemyb3.html" title="tiptap ListItem 节点">ListItem 节点</a>
      <a data-level="3" class="leve-3   doc-1    doc-2 " href="/tiptap/nnzya3qj.html" title="tiptap Mention 提及节点">Mention 提及节点</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/3uqmeazz.html" title="tiptap OrderedList 有序列节点">OrderedList 有序列节点</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/iyryuqn6.html" title="tiptap Paragraph 段落节点">Paragraph 段落节点</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/mv67fbzr.html" title="tiptap Table 表格扩展">Table 表格扩展</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/bfmjvvbz.html" title="tiptap TaskList 任务列表扩展">TaskList 任务列表</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/zqi7fvzf.html" title="tiptap TaskItem 任务列表项扩展">TaskItem 任务列表项</a>
      <a data-level="3" class="leve-3   doc-1   " href="/tiptap/ffffui3e.html" title="tiptap Text 节点">Text 节点</a>
      <a data-level="2" class="leve-2    arrow-1" href="/tiptap/marks.html" title="tiptap marks 标记">marks 标记</a>
      <a data-level="2" class="leve-2    arrow-1" href="/tiptap/extensions.html" title="tiptap extensions 扩展">extensions 扩展</a>
                 </div>
                 <div  class="clearfix"></div>
            </div>
        </div>
        <div class="clearfix"></div>
           <div class="footer">
© 2023 itxst.com All rights reserved.
</div>

<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?7e15ed96eb8133218e99de8bc1f5cb61";
  var s = document.getElementsByTagName("script")[0];
  s.parentNode.insertBefore(hm, s);
})();
</script>
       </div>
    </div> 
</body>
</html>
